/**
* @file gui_core.c
* @brief gui core
* @details 
* @author huangbin
* @version V1.0.0
* @date 2025-06-12
* @copyright All documents are copyrighted by Shenzhen Xinlongwei Technology Co., LTD.
*            They may not be used for commercial purposes without permission
*/

/*============================ INCLUDES ======================================*/

#define __GUI_MESSAGE_INHERIT__
#define __GUI_CORE_IMPLEMENT__
#include "gui_core.h"

#if defined(__clang__)
//#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wunknown-warning-option"
#   pragma clang diagnostic ignored "-Wreserved-identifier"
#   pragma clang diagnostic ignored "-Wsign-conversion"
#   pragma clang diagnostic ignored "-Wpadded"
#   pragma clang diagnostic ignored "-Wcast-qual"
#   pragma clang diagnostic ignored "-Wcast-align"
#   pragma clang diagnostic ignored "-Wmissing-field-initializers"
#   pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#   pragma clang diagnostic ignored "-Wmissing-prototypes"
#   pragma clang diagnostic ignored "-Wunused-variable"
#   pragma clang diagnostic ignored "-Wunused-parameter"
#   pragma clang diagnostic ignored "-Wgnu-statement-expression"
#   pragma clang diagnostic ignored "-Wtautological-pointer-compare"
#elif __IS_COMPILER_ARM_COMPILER_5__
#elif __IS_COMPILER_GCC__
#   pragma GCC diagnostic push
#   pragma GCC diagnostic ignored "-Wformat="
#   pragma GCC diagnostic ignored "-Wpedantic"
#endif

/*============================ MACROS ========================================*/
#undef this
#define this    (*ptThis)
/*============================ MACROFIED FUNCTIONS ===========================*/
/*============================ TYPES =========================================*/
/*============================ GLOBAL VARIABLES ==============================*/
/*============================ PROTOTYPES ====================================*/

static
void gui_before_scene_deposing_handler(void *pTarget,
                                        arm_2d_scene_player_t *ptPlayer,
                                        arm_2d_scene_t *ptScene);

/*============================ LOCAL VARIABLES ===============================*/
/*============================ IMPLEMENTATION ================================*/

ARM_NONNULL(1, 2)
arm_2d_err_t gui_init(xm_gui_t *ptThis, xm_gui_cfg_t *ptCFG)
{
    assert(NULL != ptThis);
    assert(NULL != ptCFG);

    ARM_2D_LOG_INFO(
        GUI_STACK,
        0,
        "Reference GUI",
        "Initialize Reference GUI [%p]...",
        ptThis
    );

    memset(ptThis, 0, sizeof(xm_gui_t));

    this.tCFG = *ptCFG;
    if (NULL == this.tCFG.ptDispAdapter) {
        ARM_2D_LOG_ERROR(
            GUI_STACK,
            1,
            "Reference GUI",
            "The .ptDispAdapter is NULL\r\n"
        );
        return ARM_2D_ERR_MISSING_PARAM;
    }

    if (NULL == this.tCFG.fnDispAdapterTask) {
        ARM_2D_LOG_ERROR(
            GUI_STACK,
            1,
            "Reference GUI",
            "The .fnDispAdapterTask is NULL\r\n"
        );
        return ARM_2D_ERR_MISSING_PARAM;
    }

    /* initialize the message helper */
    do {
        gui_helper_msg_cfg_t tCFG = {
            .bAllowUsingHeap = false,
        };
        gui_helper_msg_init(&this.tMSGCTRL, &tCFG);
    } while(0);

    if (    (NULL != this.tCFG.Message.ptItems)
      &&    (this.tCFG.Message.hwCount > 0)) {
        gui_helper_msg_add_items_to_pool(   &this.tMSGCTRL,
                                            this.tCFG.Message.ptItems,
                                            this.tCFG.Message.hwCount);
    }

    /* initialize user message fifo */
    gui_helper_msg_fifo_init(&this.tAppMessageFIFO);

    /* initialize message frontend */
    do {
        gui_helper_msg_key_frontend_init(   &this.MessageFrontend.tKey, 
                                            &this.tCFG.tKeyFrontend);

        gui_helper_msg_frontend_register(&this.tMSGCTRL,
                                         &this.MessageFrontend.tKey.use_as__gui_msg_frontend_t,
                                         GUI_FRONTEND_KEY);
        
        gui_helper_msg_pointer_frontend_init(&this.MessageFrontend.tPointer, 
                                             &this.tCFG.tPointerFrontend,
                                             this.tCFG.ptPipelines,
                                             this.tCFG.chPipelineNo);
        
        gui_helper_msg_frontend_register(&this.tMSGCTRL,
                                         &this.MessageFrontend.tPointer.use_as__gui_msg_frontend_t,
                                         GUI_FRONTEND_POINTER);
		gui_helper_msg_key_frontend_double_pressed_detection(&this.MessageFrontend.tKey,false); //disable double click
    } while(0);
    if (0 == this.tCFG.u4MessageBatchProcessingNumber) {
        this.tCFG.u4MessageBatchProcessingNumber = 8;
    }

    /* register before scene deposing event handler */
    arm_2d_scene_player_register_before_deposing_event_handler(
        this.tCFG.ptDispAdapter,
        gui_before_scene_deposing_handler,
        ptThis);

    ARM_2D_LOG_INFO(
        GUI_STACK,
        0,
        "Reference GUI",
        "OK\r\n"
    );

    return ARM_2D_ERR_NONE;
}

ARM_NONNULL(1)
gui_msg_key_frontend_t *gui_get_key_frontend(xm_gui_t *ptThis)
{
    assert(NULL != ptThis);

    return &this.MessageFrontend.tKey;
}

ARM_NONNULL(1)
gui_msg_pointer_frontend_t *gui_get_pointer_frontend(xm_gui_t *ptThis)
{
    assert(NULL != ptThis);

    return &this.MessageFrontend.tPointer;
}

ARM_NONNULL(1)
arm_2d_scene_player_t *gui_get_disp_adapter(xm_gui_t *ptThis)
{
    assert(ptThis);
    return this.tCFG.ptDispAdapter;
}

static
void gui_before_scene_deposing_handler(void *pTarget,
                                        arm_2d_scene_player_t *ptPlayer,
                                        arm_2d_scene_t *ptScene)
{
    xm_gui_t *ptThis = (xm_gui_t *)pTarget;

    xm_gui_on_msg_evt_t **pptItem = &this.ptEventList;
    bool bFoundItem = false;

    while(NULL != *pptItem) {
        arm_irq_safe {
            if ((*pptItem)->pTarget == (void *)ptScene) {
                /* remove this node */
                xm_gui_on_msg_evt_t *ptTargetItem = (*pptItem);
                (*pptItem) = (*pptItem)->ptNext;
                ptTargetItem->ptNext = NULL;
                bFoundItem = true;
            }
        }

        if (bFoundItem) {
            return ;
        }

        pptItem = &((*pptItem)->ptNext);
    }
}

ARM_NONNULL(1,2)
void gui_register_msg_handler(   xm_gui_t *ptThis, 
                                xm_gui_on_msg_evt_t *ptEventHandler)
{
    assert(NULL != ptThis);
    assert(NULL != ptEventHandler);

    xm_gui_on_msg_evt_t *ptItem = this.ptEventList;

    while(NULL != ptItem) {
        if (ptItem == ptEventHandler) {
            return ;
        }

        ptItem = ptItem->ptNext;
    }

    arm_irq_safe {
        /* add event handler */
        __ARM_LIST_STACK_PUSH(this.ptEventList, ptEventHandler);
    }

}

ARM_NONNULL(1,2)
void gui_unregister_msg_handler( xm_gui_t *ptThis, 
                                xm_gui_on_msg_evt_t *ptEventHandler)
{
    assert(NULL != ptThis);
    assert(NULL != ptEventHandler);

    xm_gui_on_msg_evt_t **pptItem = &this.ptEventList;
    bool bFoundItem = false;

    while(NULL != *pptItem) {
        
        arm_irq_safe {
            if ((*pptItem) == ptEventHandler) {
                /* remove this node */
                xm_gui_on_msg_evt_t *ptTargetItem = (*pptItem);
                (*pptItem) = (*pptItem)->ptNext;
                ptTargetItem->ptNext = NULL;
                bFoundItem = true;
            }
        }

        if (bFoundItem) {
            return ;
        }

        pptItem = &((*pptItem)->ptNext);
    }
}



static 
bool gui_manual_scene_switching(xm_gui_t *ptThis, gui_msg_core_t *ptMSG)
{
    /* todo */
    return true;
}

__WEAK void gui_default_message_handler(xm_gui_t *ptThis, gui_msg_core_t *ptMSG)
{
    assert(ptThis);

    if (NULL == ptMSG) {
        return ;
    }

    do {
        if (ARM_2D_INVOKE( this.tCFG.fnUserDefaultMessageHandler,
                            ARM_2D_PARAM(
                                ptThis,
                                ptMSG
                            ))) {
            /* the user has handled the message and wants to 
            * ignore the default message handler 
            */
            break;
        }

        /* todo: other default message handling */


    } while(0);

    gui_helper_msg_free(&this.tMSGCTRL, (gui_msg_t *)ptMSG);
}

__WEAK
void gui_default_message_preprocessing_handler(xm_gui_t *ptThis)
{
    assert(ptThis);

    int_fast8_t nMessageBatchProcessCount = this.tCFG.u4MessageBatchProcessingNumber;
    do {
    /* get a message */
    gui_msg_core_t *ptMSG = gui_helper_msg_get(&this.tMSGCTRL);

    if (NULL == ptMSG) {
        return ;
    }

    /* message filter */
    do {
        if (ARM_2D_INVOKE( this.tCFG.fnUserMessagePreprocessingHandler,
                            ARM_2D_PARAM(
                                ptThis,
                                ptMSG
                            ))) {
            /* the user has handled the message and wants to 
            * ignore the default message handler 
            */
            gui_helper_msg_free(&this.tMSGCTRL, (gui_msg_t *)ptMSG);
            break;
        }

        if (arm_2d_scene_player_is_switching(this.tCFG.ptDispAdapter)) {
            if (gui_manual_scene_switching(ptThis, ptMSG)) {
                gui_helper_msg_free(&this.tMSGCTRL, (gui_msg_t *)ptMSG);
                break;
            }
        }

        /* todo : other default message handling 
        if (...) {
            gui_helper_msg_free(&this.tMSGCTRL, (gui_msg_t *)ptMSG);
            break;
        }
        */

        /* call message handler */
        if (NULL != this.ptEventList) {

            bool bMessageHandled = false;
            xm_gui_on_msg_evt_t *ptItem = this.ptEventList;

            do {
                /* call message handler */
                if (ARM_2D_INVOKE(ptItem->fnHandler,
                        ARM_2D_PARAM(ptThis, ptItem->pTarget,ptMSG))) {
                    bMessageHandled = true;
                    break;
                }

                if (this.tCFG.bOnlyTheTopSceneCanHandleMessage) {
                    break;
                }

                ptItem = ptItem->ptNext;
            } while(ptItem != NULL);
            
            if (bMessageHandled) {
                gui_helper_msg_free(&this.tMSGCTRL, (gui_msg_t *)ptMSG);
            } else {
                gui_default_message_handler(ptThis, ptMSG);
            }

            break;
        }

        /* add message to the App message FIFO */
        gui_helper_msg_fifo_append(&this.tAppMessageFIFO, ptMSG);
        
    } while(0);
    } while(--nMessageBatchProcessCount);
    
}

ARM_NONNULL(1)
arm_fsm_rt_t gui_task(xm_gui_t *ptThis, int_fast8_t chFramerate) 
{
    assert(NULL != ptThis);
    assert(NULL != this.tCFG.fnDispAdapterTask);

    if (NULL == this.tCFG.fnDispAdapterTask) {
        return (arm_fsm_rt_t)ARM_2D_ERR_INVALID_PARAM;
    }

    arm_fsm_rt_t tResult = arm_fsm_rt_on_going;

    if (chFramerate == 0) {
        /* no display */
        this.Runtime.bNeedToDrawFrame = false;
        tResult = arm_fsm_rt_cpl;
        this.Runtime.bConsumeResidualMessage = true;
    } else if (chFramerate < 0) {
        /* no framerate locking */
        tResult = ARM_2D_INVOKE(this.tCFG.fnDispAdapterTask);
        this.Runtime.bNeedToDrawFrame = !(tResult == arm_fsm_rt_cpl);
        if (!this.Runtime.bNeedToDrawFrame) {
            this.Runtime.bConsumeResidualMessage = true;
        }
    } else if (this.Runtime.bNeedToDrawFrame) {
        tResult = ARM_2D_INVOKE(this.tCFG.fnDispAdapterTask);
        if (arm_fsm_rt_cpl == tResult) {
            this.Runtime.bNeedToDrawFrame = false;
            this.Runtime.bConsumeResidualMessage = true;
        }
    } else if (arm_2d_helper_is_time_out(1000 / chFramerate, &this.Runtime.lTimestamp)) {
        this.Runtime.bNeedToDrawFrame = true;
    }
    if (this.Runtime.bConsumeResidualMessage) {
        this.Runtime.bConsumeResidualMessage = false;

        do {
            gui_msg_core_t *ptMSG = gui_helper_msg_fifo_get(&this.tAppMessageFIFO);
            if (NULL == ptMSG) {
                break;
            }
            gui_default_message_handler(ptThis, ptMSG);
        } while(true);
    }
    
    /* preprocess messages */
    while (arm_fsm_rt_on_going == gui_helper_msg_task(&this.tMSGCTRL));

    do {
    #if 0
        if (!arm_2d_scene_player_is_switching(this.tCFG.ptDispAdapter)) {
            break;
        }
    #endif
        /* NOTE: When bNeedToDrawFrame is true, we are sure it is between two frames, and
        *       it is safe to do any updates on scene content. 
        */
        if (!this.Runtime.bNeedToDrawFrame) {
            /* safe to handle message */
            gui_default_message_preprocessing_handler(ptThis);
        }
    } while(0);

    return tResult;

}

ARM_NONNULL(1,2)
void gui_send_msg(xm_gui_t *ptThis,
                 gui_msg_core_t *ptMSG)
{
    assert(NULL != ptThis);
    assert(NULL != ptMSG);

    gui_helper_msg_send(&this.tMSGCTRL, ptMSG);
}

ARM_NONNULL(1)
gui_msg_t *gui_msg_new(xm_gui_t *ptThis)
{
    assert(NULL != ptThis);

    return gui_helper_msg_new(&this.tMSGCTRL);
}
ARM_NONNULL(1)
bool gui_send_pointer_evt(xm_gui_t *ptThis, 
                         uint8_t chPointerEvent,
                         uint16_t hwPointerIndex,
                         int16_t iX,
                         int16_t iY)
{
    assert(NULL != ptThis);

    return gui_helper_msg_send_pointer_event(   &this.tMSGCTRL,
                                                chPointerEvent,
                                                hwPointerIndex,
                                                iX,
                                                iY,
                                                NULL);
}

ARM_NONNULL(1)
bool gui_send_key_evt(   xm_gui_t *ptThis, 
                        uint8_t chKeyEvent,
                        uint16_t hwKeyValue)
{
    assert(NULL != ptThis);

    return gui_helper_msg_send_key_event(   &this.tMSGCTRL,
                                            chKeyEvent,
                                            hwKeyValue,
                                            NULL);
}
ARM_NONNULL(1)
bool gui_send_timer_evt(   xm_gui_t *ptThis,uint8_t chIndex)
{
    assert(NULL != ptThis);
    gui_msg_timer_evt_t *ptTimerMSG = (gui_msg_timer_evt_t *)gui_helper_msg_new(&ptThis->tMSGCTRL);
	if (NULL == ptTimerMSG) {
		return false;
	}
	ptTimerMSG->chIndex = chIndex;
	ptTimerMSG->use_as__gui_msg_core_t.ptTargetWidget = NULL;
	ptTimerMSG->use_as__gui_msg_core_t.u8ID = GUI_MSG_WIDGETS_EVT_TIMER;
    gui_helper_msg_send(&ptThis->tMSGCTRL, &ptTimerMSG->use_as__gui_msg_core_t);
    return true;	
}
ARM_NONNULL(1)
bool gui_send_communication_evt(   xm_gui_t *ptThis,uint8_t chCmdcode,uint32_t wparameter)
{
    assert(NULL != ptThis);
    gui_msg_communication_evt_t *ptMSG = (gui_msg_communication_evt_t *)gui_helper_msg_new(&ptThis->tMSGCTRL);
	if (NULL == ptMSG) {
		return false;
	}
	ptMSG->chCmdCode = chCmdcode;
	ptMSG->wParameter = wparameter;
	ptMSG->use_as__gui_msg_core_t.ptTargetWidget = NULL;
	ptMSG->use_as__gui_msg_core_t.u8ID = GUI_MSG_WIDGETS_EVT_COMM;
    gui_helper_msg_send(&ptThis->tMSGCTRL, &ptMSG->use_as__gui_msg_core_t);
    return true;	
}
ARM_NONNULL(1)
gui_msg_core_t *gui_msg_handling_begin(xm_gui_t *ptThis)
{
    assert(NULL != ptThis);

    return gui_helper_msg_fifo_peek(&this.tAppMessageFIFO);
}

ARM_NONNULL(1)
void gui_msg_handling_end(xm_gui_t *ptThis, bool bHandled)
{
    assert(NULL != ptThis);

    gui_msg_core_t *ptMSG = gui_helper_msg_fifo_get(&this.tAppMessageFIFO);

    if (bHandled) {
        gui_helper_msg_free( &this.tMSGCTRL, (gui_msg_t *)ptMSG);
        return ;
    }

    gui_default_message_handler(ptThis, ptMSG);
}


